package com.szwistar.common.os;

import com.szwistar.common.datastruct.LOG;
import com.szwistar.common.datastruct.Utils;

import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 进程守护服务
 * @author Shenzhen Wistar
 */
public class ProcessDeamon {
	/**
	 * 应用层回调接口
	 */
	public static interface ProcessDeamonEventHandler {
		/**
		 * 当进程关闭时回调
		 * @param exitCode 进程退出码
		 */
		void onProcessTerminated(int exitCode);

		/**
		 * 当进程 stdout 有数据可读时回调
		 * @param ch 数据
		 */
		void stdout(byte ch);

		/**
		 * 当进程 stderr 有数据可读时回调
		 * @param ch 数据
		 */
		void stderr(byte ch);
	}

	protected Process proc = null;
	protected DataOutputStream stdin = null;
	protected int exitCode = 0;

	public int getExitCode() { return exitCode; }

	/**
	 * 向进程 stdin 写数据
	 * @param data 数据
	 */
	public void writeStdin(byte[] data) {
		if(stdin == null || Utils.isEmpty(data)) { return; }
		try {
			stdin.write(data); stdin.flush();
		} catch (IOException e) {
		}
	}

	/**
	 * 停止进程，并关闭所有资源
	 */
	public void killProcess() {
		FileUtils.close(stdin); stdin = null;
		if(proc != null) { proc.destroy(); proc = null; }
	}

	/**
	 * 守护线程是否已经关闭
	 */
	public boolean isClosed() {
		return proc == null;
	}

	protected ProcessDeamon(final Process proc, final int timeout, final ProcessDeamonEventHandler eventHandler) {
		this.proc = proc;

		// 获取 stdin
		stdin = new DataOutputStream(proc.getOutputStream());

		// 启动一个读 stdout 数据的工作线程
		TimerEx.runLater(new Runnable() {
			@Override
			public void run() {
				InputStream ins = proc.getInputStream();
				try {
					while(true) {
						int ch = ins.read();
						if(ch < 0) { break; }
						if(eventHandler != null) { eventHandler.stdout((byte)ch); }
					}
				} catch (Exception e) {
				} finally {
					FileUtils.close(ins);
				}
			}
		});

		// 启动一个读 stderr 数据的工作线程
		/*
		TimerEx.runLater(new Runnable() {
			@Override
			public void run() {
				InputStream ins = proc.getErrorStream();
				try {
					while(true) {
						int ch = ins.read();
						if(ch < 0) { break; }
						if(eventHandler != null) { eventHandler.stderr((byte)ch); }
					}
				} catch (Exception e) {
				} finally {
					FileUtils.close(ins);
				}
			}
		});
		*/

		// 启动一个守护线程，用于等待进程退出
		TimerEx.runLater(new Runnable() {
			@Override
			public void run() {
				try {
					if(timeout > 0 && Os.getJDKVersion() >= 1008) {
						// 等待 shell 退出完成
						if (proc.waitFor(timeout, TimeUnit.MILLISECONDS)) {
							// 取得错误码
							exitCode = proc.exitValue();
						} else {
							exitCode = -254;
							LOG.error("命令执行超时！强制退出！");
							killProcess();
						}
					} else {
						// 等待 shell 退出完成，并取得错误码
						exitCode = proc.waitFor();
					}
				} catch (InterruptedException e) {
					exitCode = -255;
				} finally {
					// 回调
					if(eventHandler != null) { eventHandler.onProcessTerminated(exitCode); }
					// 清理
					killProcess();
				}
			}
		});
	}

	/**
	 * 执行一个 shell 命令，创建并返回对应的进程守护服务
	 * @param cmds 命令行数组
	 * @param timeout 进程运行超时时间(ms)，如果超过此时间，则强行中止；=0 则表示永远等待
	 * @param asRoot 是否以 root 权限运行
	 * @param redirectStderrToStdout 是否需要将 stderr 重定向到 stdout
	 * @return 通信守护线程
	 */
	public static ProcessDeamon start(String[] cmds, int timeout, boolean asRoot, File workDir, Map<String, String> env, boolean redirectStderrToStdout, ProcessDeamonEventHandler eventHandler) {
		if(Utils.isEmpty(cmds)) { return null; }
		Process proc = null;

		try {
			// 创建进程
			ProcessBuilder pb = new ProcessBuilder(cmds);
			// 如果需要，设置运行环境
			if(env != null) { pb.environment().putAll(env); }
			// 如果需要，重定向 stderr 到 stdout
			if(redirectStderrToStdout) { pb.redirectErrorStream(true); }
			// 如果需要，设置运行目录
			if(PathUtils.existDir(workDir)) { pb.directory(workDir); }
			// 启动进程
			proc = pb.start();
			// 创建进程守护服务
			return new ProcessDeamon(proc, timeout, eventHandler);
		} catch (Exception e) {
			// 出错，释放资源
			if(proc != null) { proc.destroy(); }
			LOG.error("启动进程失败！" + e.getMessage());
			return null;
		}
	}


	// for test only
	public static void main(String[] args) {
		ProcessDeamon deamon;
		ProcessDeamonEventHandler eventHandler = new ProcessDeamonEventHandler() {
			@Override
			public void onProcessTerminated(int exitCode) {}

			@Override
			public void stdout(byte ch) {
				System.out.print((char)ch);
			}

			@Override
			public void stderr(byte ch) {}
		};

		//deamon = ProcessDeamon.start(new String[]{"cmd", "/c", "dir"}, 10000, true, null, null, true);
		//deamon = ProcessDeamon.start(new String[]{"cmd", "/c", "type", "con"}, 10000, true, null, null, true, eventHandler);
		//deamon = ProcessDeamon.start(new String[]{"ping", "127.0.0.1", "-t"}, 10000, true, null, null, true, eventHandler);
		deamon = ProcessDeamon.start(new String[]{"lua", "test-engine.lua"}, 10000, true, null, null, true, eventHandler);
		while(deamon != null && !deamon.isClosed()) {
			Os.sleep(500);
			deamon.writeStdin("hello\r\n".getBytes());
		}

		System.exit(0);
	}
}
